/**
 * @param {string} s
 * @param {string} t
 * @return {string}
 */
 var minWindow = function(s, t) {
//   思路：用左右两个指针遍历s字符串，当滑动窗口中的字符不能覆盖t中的字符时，右指针右移，扩大窗口，把右边的字符加入滑动窗口，当滑动窗口中的字符能覆盖t中的字符时，不断左移左指针，缩小窗口，直到窗口中的字符刚好能覆盖t中的字符，这个时候在左移就不能覆盖t中的字符了，在指针移动的过程中，不断更新最小覆盖子串
// 复杂度：时间复杂度o(n)，n是s的长度，空间复杂度o(t)，t是字符集的大小
  let need = {} // 需要覆盖的字符串各个字符和其个数所对应的键值对
  let window = {} // 滑动窗口中的字符串各个字符和其所对应的个数的键值对
  for(let a of t){
    need[a] = (need[a] || 0) + 1 // 统计t中各个字符与其对应的个数的键值对 初始化need字典
  }
  let left = 0,
    right = 0 // 定义左右指针
  let valid = 0 // 滑动窗口中覆盖的字符种类的数量
  let start = 0 // 最小覆盖子串的起始索引值
  let len = Number.MAX_VALUE // 最小覆盖子串的长度 先取最大值
  while(right < s.length){
    let c = s[right] // 进入滑动窗口的右边的字符
    right++
    if(need[c]){ // 如果右边的字符在need字符中 更新window中的字符数
      window[c] = (window[c] || 0) + 1
      if(window[c] === need[c]){ // 当前窗口中的该字符数等于need中的该字符数
        valid++ // 覆盖字符数种类加1
      }
    }
    while(valid === Object.keys(need).length){ // 当窗口中的覆盖字符串的数量和need中的一致的时候 就收缩窗口
      if(right - left < len){ // 当前窗口长度小于 窗口的长度len 因为right在前面已经++了 所以这里不写 + 1 
        start = left
        len = right - left
      }
      // 覆盖了 就移动左指针
      let d = s[left]
      left++
      if(need[d]){ // 移出的元素在need中 更新window中的字符数
        if(window[d] === need[d]){ // 如果当前的窗口和需要的字符数量一致时,字符种类-1
          valid--
        }
        window[d]--
      }
    }
  }
  return len === Number.MAX_VALUE ? "" : s.slice(start,start+len)
};